package com.magenta.mrs.util;

import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/*
* Project: MaxOptRA v1.1
* User: Ivan Usalko
* Date: 15.07.2009 20:51:56
*
* Copyright (c) 1999-2009 Magenta Corporation Ltd. All Rights Reserved.
* Magenta Technology proprietary and confidential.
* Use is subject to license terms.
*/
public class WillBeCheaperUK implements GeocodingRequestsCache {

    private static int DEFAULT_CACHE_SIZE = 1000;
    private static float LOADFACTOR = 0.75f;
    private static int CONCLEVEL = 16;

    private LocalRequestsCache requestsCache;

    class LocalRequestsCache extends ConcurrentHashMap<String, SurfaceCoordinates> {

        /**
         * @see <a href=http://www.govtalk.gov.uk/gdsc/html/frames/PostCode.htm>UK Postcode.</a>
         */
        public final Pattern POSTCODE = Pattern.compile("(GIR 0AA)|((([A-Z-[QVX]][0-9][0-9]?)|(([A-Z-[QVX]][A-Z-[IJZ]][0-9][0-9]?)|(([A-Z-[QVX]][0-9][A-HJKSTUW])|([A-Z-[QVX]][A-Z-[IJZ]][0-9][ABEHMNPRVWXY])))) [0-9][A-Z-[CIKMOV]]{2})");

        private Map<String, String> fullKeyToPartial = new ConcurrentHashMap<String, String>();

        private final int capacity;

        LocalRequestsCache(int initialCapacity, float loadFactor, int concurrencyLevel) {
            super(initialCapacity, loadFactor, concurrencyLevel);
            this.fullKeyToPartial = new ConcurrentHashMap<String, String>(initialCapacity, loadFactor, concurrencyLevel);
            capacity = initialCapacity;
        }

        String extractPostCode(String addressInfo) {
            String cachedAnswer = fullKeyToPartial.get(addressInfo);
            if (cachedAnswer != null) {
                return cachedAnswer;
            }
            Matcher postCodeMatcher = POSTCODE.matcher(addressInfo);
            if (postCodeMatcher.find()) {
                MatchResult matchResult = postCodeMatcher.toMatchResult();
                int startIndex = matchResult.start();
                int stopIndex = matchResult.end();
                return addressInfo.substring(startIndex, stopIndex);
            }
            return null;
        }

        private void managePut(int count) {
            int removed = count;
            if (size() + count >= capacity) {
                Iterator<Entry<String, SurfaceCoordinates>> iter = entrySet().iterator();
                while (iter.hasNext() && removed-- > 0) {
                    String key = iter.next().getKey();
                    iter.remove();
                    Iterator<Entry<String, String>> dependentIter = fullKeyToPartial.entrySet().iterator();
                    while (dependentIter.hasNext()) {
                        if (key.equals(dependentIter.next().getValue())) {
                            dependentIter.remove();
                        }
                    }
                }
            }
        }

        private String removeMultiSpaces(String addressInfo) {
            return addressInfo.replaceAll("\\s+", " ");
        }

        @Override
        public SurfaceCoordinates put(String key, SurfaceCoordinates value) {
            managePut(1);
            String newKey = extractPostCode(removeMultiSpaces(key));
            if (newKey != null) {
                fullKeyToPartial.put(key, newKey);
                return super.put(newKey, value);
            }
            return super.put(key, value);
        }

        @Override
        public SurfaceCoordinates putIfAbsent(String key, SurfaceCoordinates value) {
            managePut(1);
            String newKey = extractPostCode(removeMultiSpaces(key));
            if (newKey != null) {
                fullKeyToPartial.put(key, newKey);
                return super.putIfAbsent(newKey, value);
            }
            return super.putIfAbsent(key, value);
        }

        @Override
        public SurfaceCoordinates get(Object key) {
            String newKey = extractPostCode(removeMultiSpaces((String) key));
            if (newKey != null) {
                return super.get(newKey);
            }
            return super.get(key);
        }
    }

    public WillBeCheaperUK(int cacheSize) {
        requestsCache = new LocalRequestsCache(cacheSize, LOADFACTOR, CONCLEVEL);
    }

    public WillBeCheaperUK() {
        this(DEFAULT_CACHE_SIZE);
    }

    public void put(String addressInfo, SurfaceCoordinates mapPoint) {
        requestsCache.put(addressInfo, mapPoint);
    }

    public SurfaceCoordinates get(String addressInfo) {
        return requestsCache.get(addressInfo);
    }
}
